﻿using System;
using System.IO;
using System.Xml;
using Narwhunderful.Aphelia.Framework.IO.Serialization.Binary;
using Narwhunderful.Aphelia.Framework.IO.Serialization.Xml;

namespace Narwhunderful.Aphelia.Framework.IO.Serialization
{

    public sealed class Serializer
    {
        private IPropertySerializer _serializer;
        private IPropertyDeserializer _deserializer;
        private PropertyProvider _propertyProvider;
        private string _rootName;

        public Serializer()
        {
            Initialize(new SerializationXmlSettings());
        }

        public Serializer(bool binarySerialization)
        {
            if (binarySerialization)
            {
                Initialize(new SerializationBinarySettings());
            }
            else
            {
                Initialize(new SerializationXmlSettings());
            }
        }

        public Serializer(BinarySerializationMode binaryMode)
        {
            Initialize(new SerializationBinarySettings(binaryMode));
        }

        public Serializer(SerializationXmlSettings settings)
        {
            if (settings == null) throw new ArgumentNullException("settings");
            Initialize(settings);
        }

        public Serializer(SerializationBinarySettings settings)
        {
            if (settings == null) throw new ArgumentNullException("settings");
            Initialize(settings);
        }

        public Serializer(IPropertySerializer serializer, IPropertyDeserializer deserializer)
        {
            if (serializer == null) throw new ArgumentNullException("serializer");
            if (deserializer == null) throw new ArgumentNullException("deserializer");
            _serializer = serializer;
            _deserializer = deserializer;
        }

        public PropertyProvider PropertyProvider
        {
            get
            {
                if (_propertyProvider == null) _propertyProvider = new PropertyProvider();
                return _propertyProvider;
            }
            set { _propertyProvider = value; }
        }

        public string RootName
        {
            get
            {
                if (_rootName == null) _rootName = "Root";
                return _rootName;
            }
            set { _rootName = value; }
        }

        private void Initialize(SerializationXmlSettings settings)
        {

            PropertyProvider.PropertiesToIgnore = settings.AdvancedSettings.PropertiesToIgnore;
            PropertyProvider.AttributesToIgnore = settings.AdvancedSettings.AttributesToIgnore;
            RootName = settings.AdvancedSettings.RootName;

            ISimpleValueConverter simpleValueConverter = settings.AdvancedSettings.SimpleValueConverter ??
                                                         DefaultInitializer.GetSimpleValueConverter(settings.Culture);

            ITypeNameConverter typeNameConverter = settings.AdvancedSettings.TypeNameConverter ??
                                                   DefaultInitializer.GetTypeNameConverter(
                                                       settings.IncludeAssemblyVersionInTypeName,
                                                       settings.IncludeCultureInTypeName,
                                                       settings.IncludePublicKeyTokenInTypeName);
            XmlWriterSettings xmlWriterSettings = DefaultInitializer.GetXmlWriterSettings(settings.Encoding);
            XmlReaderSettings xmlReaderSettings = DefaultInitializer.GetXmlReaderSettings();

            var reader = new DefaultXmlReader(typeNameConverter, simpleValueConverter, xmlReaderSettings);
            var writer = new DefaultXmlWriter(typeNameConverter, simpleValueConverter, xmlWriterSettings);

            _serializer = new XmlPropertySerializer(writer);
            _deserializer = new XmlPropertyDeserializer(reader);
        }

        private void Initialize(SerializationBinarySettings settings)
        {

            PropertyProvider.PropertiesToIgnore = settings.AdvancedSettings.PropertiesToIgnore;
            PropertyProvider.AttributesToIgnore = settings.AdvancedSettings.AttributesToIgnore;

            RootName = settings.AdvancedSettings.RootName;

            ITypeNameConverter typeNameConverter = settings.AdvancedSettings.TypeNameConverter ??
                                                   DefaultInitializer.GetTypeNameConverter(
                                                       settings.IncludeAssemblyVersionInTypeName,
                                                       settings.IncludeCultureInTypeName,
                                                       settings.IncludePublicKeyTokenInTypeName);

            IBinaryReader reader;
            IBinaryWriter writer;

            if (settings.Mode == BinarySerializationMode.Burst)
            {
                // Burst mode
                writer = new BurstBinaryWriter(typeNameConverter, settings.Encoding);
                reader = new BurstBinaryReader(typeNameConverter, settings.Encoding);
            }
            else
            {
                // Size optimized mode
                writer = new SizeOptimizedBinaryWriter(typeNameConverter, settings.Encoding);
                reader = new SizeOptimizedBinaryReader(typeNameConverter, settings.Encoding);
            }

            _deserializer = new BinaryPropertyDeserializer(reader);
            _serializer = new BinaryPropertySerializer(writer);
        }

        public void Serialize(object data, string filename)
        {
            using (Stream stream = new FileStream(filename, FileMode.Create, FileAccess.Write))
            {
                Serialize(data, stream);
            }
        }

        public void Serialize(object data, Stream stream)
        {
            if (data == null) throw new ArgumentNullException("data");

            var factory = new PropertyFactory(PropertyProvider);

            Property property = factory.CreateProperty(RootName, data);

            try
            {
                _serializer.Open(stream);
                _serializer.Serialize(property);
            }
            finally
            {
                _serializer.Close();
            }
        }

        public object Deserialize(string filename)
        {
            using (var stream = new FileStream(filename, FileMode.Open, FileAccess.Read))
            {
                return Deserialize(stream);
            }
        }

        public object Deserialize(Stream stream)
        {
            try
            {
                // Deserialize Property
                _deserializer.Open(stream);
                Property property = _deserializer.Deserialize();
                _deserializer.Close();

                // create object from Property
                var factory = new ObjectFactory();
                return factory.CreateObject(property);
            }
            catch (Exception exception)
            {
                // corrupted Stream
                throw new DeserializingException(
                    "An error occured during the deserialization. Details are in the inner exception.", exception);
            }
        }

    }
}
